/*
 * @Description: lvgl_app
 * @Version: v0.0.1
 * @Autor: tangwc
 * @Date: 2022-09-24 22:49:57
 * @LastEditors: tangwc
 * @LastEditTime: 2022-12-20 21:15:10
 * @FilePath: \esp32_lvgl\main\app_main.c
 *
 *  Copyright (c) 2022 by tangwc, All Rights Reserved.
 */
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

#include "lvgl/lvgl.h"
// #include "lv_examples/src/lv_demo_widgets/lv_demo_widgets.h"
// #include "lv_examples/src/lv_demo_benchmark/lv_demo_benchmark.h"
#include "lv_examples/src/lv_demo_keypad_encoder/lv_demo_keypad_encoder.h"

#include "esp_lcd_rm69330.h"
#include "esp_lcd_touch.h"
#include "realme_watch_touch.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "driver/i2c.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_vfs.h"
#include "esp_spiffs.h"
#include "nvs_flash.h"

#include "unity.h"
#include "unity_test_runner.h"

#define TAG "lvgl_demo"

#define LV_TICK_PERIOD_MS 1

#define LV_PIN_NUM_LCD_CS (17)
#define LV_PIN_NUM_LCD_CLK (16)
#define LV_PIN_NUM_LCD_MOSI (14)
#define LV_PIN_NUM_LCD_MISO (-1)

#define LV_PIN_NUM_LCD_DC (15)
#define LV_PIN_NUM_LCD_RST (18)
#define LV_DISP_BUF_SIZE (LV_HOR_RES_MAX * 80)

#define SPI_LCD_HOST SPI2_HOST

#define SPI_TFT_CLOCK_SPEED_HZ 80 * 1000 * 1000 // 80MHz
#define SPI_BUS_MAX_TRANSFER_SZ (1 << 18)
#define SPI_TRANSACTION_POOL_SIZE 50	/* maximum number of DMA transactions simultaneously in-flight */

#define LV_PIN_NUM_TOUCH_SCL   (13)
#define LV_PIN_NUM_TOUCH_SDA   (12)
#define LV_PIN_NUM_TOUCH_RST   (11)
#define LV_PIN_NUM_TOUCH_INT   (10)

#define I2C_TOUCH_HOST I2C_NUM_0

SemaphoreHandle_t xGuiSemaphore;
static lv_disp_buf_t disp_buf; // contains internal graphic buffer(s) called draw buffer(s)
static lv_disp_drv_t disp_drv;      // contains callback functions
static esp_lcd_panel_handle_t panel_handle = NULL;
static esp_lcd_touch_handle_t tp = NULL;

/*用定时器给LVGL提供时钟*/
static void lv_tick_task(void *arg)
{
    (void)arg;
    lv_tick_inc(LV_TICK_PERIOD_MS);
}

IRAM_ATTR static bool notify_refresh_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
    lv_disp_drv_t *disp_driver = (lv_disp_drv_t *)user_ctx;
    lv_disp_flush_ready(disp_driver);
    return false;
}

static void lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
{
    esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) drv->user_data;
    int offsetx1 = area->x1;
    int offsetx2 = area->x2;
    int offsety1 = area->y1;
    int offsety2 = area->y2;
    // copy a buffer's content to a specific area of the display
    esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map);
}

static void lvgl_rounder_cb(lv_disp_drv_t *disp_drv, lv_area_t *area)
{
	/* Update the areas as needed.
	 * For example it makes the area to start only on 8th rows and have Nx8 pixel height.
	 */
	if (area->x1 % 2 != 0)
		area->x1 += 1;
	if (area->y1 % 2 != 0)
		area->y1 += 1;

	uint32_t w = (area->x2 - area->x1 + 1);
	uint32_t h = (area->y2 - area->y1 + 1);
	if (w % 2 != 0)
		area->x2 -= 1;
	if (h % 2 != 0)
		area->y2 -= 1;
}


static bool lvgl_touch_cb(lv_indev_drv_t * drv, lv_indev_data_t * data)
{
    uint16_t touchpad_x[1] = {0};
    uint16_t touchpad_y[1] = {0};
    uint8_t touchpad_cnt = 0;

    /* Read touch controller data */
    esp_lcd_touch_read_data(drv->user_data);

    /* Get coordinates */
    bool touchpad_pressed = esp_lcd_touch_get_coordinates(drv->user_data, touchpad_x, touchpad_y, NULL, &touchpad_cnt, 1);

    if (touchpad_pressed && touchpad_cnt > 0) {
        data->point.x = touchpad_x[0];
        data->point.y = touchpad_y[0];
        data->state = LV_INDEV_STATE_REL;
    } else {
        data->state = LV_INDEV_STATE_PR;
    }

    return false;
}

static void gui_task(void *pvParameter)
{
    (void) pvParameter;

    xGuiSemaphore = xSemaphoreCreateMutex();
    ESP_LOGI(TAG, "Initialize LVGL library");

    lv_init(); // lvgl内核初始化

    // lvgl_driver_init(); // lvgl显示接口初始化

    lv_color_t* buf1 = heap_caps_malloc(LV_DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
    assert(buf1);
    // lv_color_t* buf2 = heap_caps_malloc(LV_DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
    // assert(buf2);

    lv_disp_buf_init(&disp_buf, buf1, NULL, LV_DISP_BUF_SIZE);

    lv_disp_drv_init(&disp_drv);
    disp_drv.hor_res = LV_HOR_RES_MAX;
    disp_drv.ver_res = LV_VER_RES_MAX;
    disp_drv.flush_cb = lvgl_flush_cb;
    disp_drv.rounder_cb = lvgl_rounder_cb;
    disp_drv.buffer = &disp_buf;
    disp_drv.user_data = panel_handle;
    lv_disp_t *disp = lv_disp_drv_register(&disp_drv);

/* Register an input device when enabled on the menuconfig */
    lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.read_cb = lvgl_touch_cb;
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.disp = disp;
    indev_drv.user_data = tp;
    lv_indev_drv_register(&indev_drv);

    const esp_timer_create_args_t periodic_timer_args = {
        .callback = &lv_tick_task,
        .name = "periodic_gui"};
    esp_timer_handle_t periodic_timer;
    ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer));
    ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, LV_TICK_PERIOD_MS * 1000));

    // lv_demo_widgets();
    // lv_demo_benchmark();
    lv_demo_keypad_encoder();

    while (1)
    {
        /* Delay 1 tick (assumes FreeRTOS tick is 10ms */
        vTaskDelay(pdMS_TO_TICKS(10));

        /* Try to take the semaphore, call lvgl related function on success */
        if (pdTRUE == xSemaphoreTake(xGuiSemaphore, portMAX_DELAY))
        {

            lv_task_handler();
            xSemaphoreGive(xGuiSemaphore);
        }
    }
}


void app_main(void)
{
	// 初始化nvs用于存放wifi或者其他需要掉电保存的东西
	esp_err_t ret = nvs_flash_init();
	if (ret == ESP_ERR_NVS_NO_FREE_PAGES)
	{
		ESP_ERROR_CHECK(nvs_flash_erase());
		ret = nvs_flash_init();
	}
	ESP_ERROR_CHECK(ret);
    
    ESP_LOGI(TAG, "Initialize SPI bus");
    spi_bus_config_t spi_buscfg = {
        .sclk_io_num = LV_PIN_NUM_LCD_CLK,
        .mosi_io_num = LV_PIN_NUM_LCD_MOSI,
        .miso_io_num = LV_PIN_NUM_LCD_MISO,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = SPI_BUS_MAX_TRANSFER_SZ,
    };
    ESP_ERROR_CHECK(spi_bus_initialize(SPI_LCD_HOST, &spi_buscfg, SPI_DMA_CH_AUTO));

    ESP_LOGI(TAG, "Install panel IO");
    esp_lcd_panel_io_handle_t io_handle = NULL;
    esp_lcd_panel_io_spi_config_t io_spi_config = {
        .dc_gpio_num = LV_PIN_NUM_LCD_DC,
        .cs_gpio_num = LV_PIN_NUM_LCD_CS,
        .pclk_hz = SPI_TFT_CLOCK_SPEED_HZ,
        .lcd_cmd_bits = 8,
        .lcd_param_bits = 8,
        .spi_mode = 0,
        .trans_queue_depth = SPI_TRANSACTION_POOL_SIZE,
        .on_color_trans_done = notify_refresh_ready,
        .user_ctx = &disp_drv,
    };

    // Attach the LCD to the SPI bus
    ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI_LCD_HOST, &io_spi_config, &io_handle));

    esp_lcd_panel_dev_config_t panel_config = {
        .reset_gpio_num = LV_PIN_NUM_LCD_RST,
        .color_space = ESP_LCD_COLOR_SPACE_BGR,
        .bits_per_pixel = 16,
    };

    ESP_LOGI(TAG, "Install RM69330 panel driver");
    ESP_ERROR_CHECK(esp_lcd_new_panel_rm69330(io_handle, &panel_config, &panel_handle));

    ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
    ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));

    ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, false));
    ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, true));
    ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel_handle, 14, 0));
    // user can flush pre-defined pattern to the screen before we turn on the screen or backlight
    ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));

    // touch panel
    ESP_LOGI(TAG, "Initialize I2C bus");
    i2c_config_t i2c_config = {
        .mode = I2C_MODE_MASTER,
        .sda_io_num = LV_PIN_NUM_TOUCH_SDA,
        .scl_io_num = LV_PIN_NUM_TOUCH_SCL,
        .sda_pullup_en = GPIO_PULLUP_ENABLE,
        .scl_pullup_en = GPIO_PULLUP_ENABLE,
        .master.clk_speed = 1 * 1000,
    };

    ESP_LOGI(TAG, "Setting I2C master configuration...");
    ESP_ERROR_CHECK(i2c_param_config(I2C_TOUCH_HOST, &i2c_config));
    ESP_LOGI(TAG, "Installing I2C master driver...");
    ESP_ERROR_CHECK(i2c_driver_install(I2C_TOUCH_HOST, I2C_MODE_MASTER, 0, 0, 0));

    esp_lcd_panel_io_handle_t tp_io_handle = NULL;
    esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_REALME_CONFIG();
    // Attach the TOUCH to the i2c bus
    ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)I2C_TOUCH_HOST, &tp_io_config, &tp_io_handle));

    esp_lcd_touch_config_t tp_cfg = {
        .x_max = LV_HOR_RES_MAX,
        .y_max = LV_HOR_RES_MAX,
        .rst_gpio_num = LV_PIN_NUM_TOUCH_RST,
        .int_gpio_num = LV_PIN_NUM_TOUCH_INT,
        .levels = {
            .reset = 0,
            .interrupt = 0,
        },
        .flags = {
            .swap_xy = 0,
            .mirror_x = 1,
            .mirror_y = 1, // upside down
        },
    };

    ESP_LOGI(TAG, "Initialize touch controller");
    ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_realme(tp_io_handle, &tp_cfg, &tp));
	/*创建lvgl任务显示*/
	xTaskCreatePinnedToCore(gui_task, "gui task", 4096*2, NULL, 0, NULL, 1);
}
